package system;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.LinkedList;
import java.util.Random;

import org.uncommons.maths.random.ExponentialGenerator;

import item.Samochod;
import kolejka.Kolejka;
import stanowisko.Stanowisko;
import utils.Logger;

public class SystemPS {
	
	private ExponentialGenerator generatorPrzyjazdowSamochodow;
	private ExponentialGenerator generatorObslugiSamochodu;
	private Logger log;
	private int liczbaSamochodow;
	private int liczbaSamochodowObsluzonych = 0;
	private double czestotliwoscPojawianiaSieZgloszen;
	
	private LinkedList<Stanowisko<Samochod>> myjnie;
	private Kolejka<Samochod> kolejkaSamochodow;
	private LinkedList<Samochod> listaSamochodowDoPrzybycia;
	
	public SystemPS(Kolejka<Samochod> kolejkaSamochodow, double lambda, double mi, int liczbaSamochodow, boolean czyLogowac) {
		this.kolejkaSamochodow = kolejkaSamochodow;
		this.liczbaSamochodow = liczbaSamochodow;		
		this.generatorPrzyjazdowSamochodow = new ExponentialGenerator(lambda, new Random(System.currentTimeMillis()));
		this.generatorObslugiSamochodu = new ExponentialGenerator(mi, new Random(System.currentTimeMillis()+1));
		this.log = new Logger(!czyLogowac);
		this.myjnie = new LinkedList<Stanowisko<Samochod>>();
	}

	public double symulacja() {

		double lacznyCzasCzekaniaWKolejce = 0;
		double aktualnyCzas = 0;
		double czasOstatniegoZdarzenia = 0;
		
		listaSamochodowDoPrzybycia = generujlisteSamochodowDoPrzybycia(liczbaSamochodow);	
		
		while (!listaSamochodowDoPrzybycia.isEmpty() || !this.kolejkaSamochodow.czyPusty() || !this.czyWszystkieMyjniePuste()) {
			
			umiescSamochodyWKolejce(aktualnyCzas);
			
			for(Stanowisko<Samochod> myjnia : this.myjnie) {
				if(!myjnia.czyPusty() && aktualnyCzas >= myjnia.dajCzasRozpoczeciaMyciaSamochodu() + myjnia.dajCzasObslugi() ) {
						lacznyCzasCzekaniaWKolejce += myjnia.dajCzasRozpoczeciaMyciaSamochodu() - myjnia.dajAktualnyObiekt().dajCzasPrzybycia();
						this.log.logCarWashed(aktualnyCzas, myjnia.dajId(), myjnia.dajAktualnyObiekt(), kolejkaSamochodow.dajWszystkie());
						myjnia.usunObiekt();
				}
			}
				
			for(Stanowisko<Samochod> myjnia : this.myjnie) {
				if (myjnia.czyPusty() && !this.kolejkaSamochodow.czyPusty()) {
					
						Samochod samochodDoMycia = this.kolejkaSamochodow.usunPierwszy();
						myjnia.ustawAktualnyObiekt(samochodDoMycia);
						
						myjnia.ustawCzasRozpoczeciaMyciaSamochodu(aktualnyCzas);
						
						this.log.logCarStartedToWash(aktualnyCzas, myjnia.dajId(), myjnia.dajAktualnyObiekt(), kolejkaSamochodow.dajWszystkie());
						break;
				}
			}

			czasOstatniegoZdarzenia = aktualnyCzas;
			aktualnyCzas = dajCzasNajblizszegoZdarzenia();
		}
		
		double sredniCzasOczekiwaniaWKolejce = lacznyCzasCzekaniaWKolejce / (double)liczbaSamochodow;
		
		System.out.println("Srednia liczba zgloszen w kolejce (Nb): " + czestotliwoscPojawianiaSieZgloszen * sredniCzasOczekiwaniaWKolejce);
		System.out.println("Sredni czas oczekiwania w kolejce (Tb): " + sredniCzasOczekiwaniaWKolejce);
		System.out.println("Wzgledna zdolnosc obslugi systemu (q): " + liczbaSamochodowObsluzonych/(double)liczbaSamochodow);
		System.out.println("Bezwzgledna zdolnosc obslugi systemu (A): " + liczbaSamochodowObsluzonych/czasOstatniegoZdarzenia);
		
		return lacznyCzasCzekaniaWKolejce / (double)liczbaSamochodow;
	}

	private boolean czyWszystkieMyjniePuste() {
		
		for(Stanowisko<Samochod> myjnia : this.myjnie) {
			if(!myjnia.czyPusty()) {
				return false;
			}
		}
		
		return true;
	}

private LinkedList<Samochod> generujlisteSamochodowDoPrzybycia(int liczbaSamochodow) {
		
		LinkedList<Samochod> listaSamochodowDoPrzybycia = new LinkedList<Samochod>();
		
		double ostatniCzasPrzybycia = 0;
		
		for (int i = 1; i <= liczbaSamochodow; i++) {
			
			ostatniCzasPrzybycia += this.generatorPrzyjazdowSamochodow.nextValue();
		
			listaSamochodowDoPrzybycia.add(new Samochod(i, ostatniCzasPrzybycia));			
		}
		
		this.czestotliwoscPojawianiaSieZgloszen = (double)liczbaSamochodow / ostatniCzasPrzybycia;
		
		return listaSamochodowDoPrzybycia;
	}

	private void umiescSamochodyWKolejce(double aktualnyCzas) {
		
		while(!listaSamochodowDoPrzybycia.isEmpty() && listaSamochodowDoPrzybycia.getFirst().dajCzasPrzybycia() <= aktualnyCzas) {
			
			Samochod dodawanySamochod = listaSamochodowDoPrzybycia.removeFirst();
			
			
				this.kolejkaSamochodow.dodaj(dodawanySamochod);	
				liczbaSamochodowObsluzonych++;
			
			
			this.log.logAddItemToQueue(dodawanySamochod.dajCzasPrzybycia(), dodawanySamochod, this.kolejkaSamochodow.dajWszystkie());
		}
	}
	
	private double dajCzasNajblizszegoZdarzenia() {
		
		double czasNajblizszegoZdarzenia = Double.MAX_VALUE;

		if(!this.listaSamochodowDoPrzybycia.isEmpty()) {
			czasNajblizszegoZdarzenia = this.listaSamochodowDoPrzybycia.getFirst().dajCzasPrzybycia();
		}
		
		for(Stanowisko<Samochod> myjnia : this.myjnie) {
			
			if(!myjnia.czyPusty() && myjnia.dajCzasRozpoczeciaMyciaSamochodu() + myjnia.dajCzasObslugi() < czasNajblizszegoZdarzenia) {
				czasNajblizszegoZdarzenia = myjnia.dajCzasRozpoczeciaMyciaSamochodu() + myjnia.dajCzasObslugi();
			}
			
		}
		
		return czasNajblizszegoZdarzenia;
	}

}
